package metodos;

import Compilador.Plantilla;
import VistaConexion.MuestraArbol;
import datos.Datos;
import datos.Regla;
import java.util.ArrayList;

public class ID3 extends Metodo {

    Datos datos; //contiene todos los datos con los que se van a trabajar
    String[] titulos; //contiene los titulos localmente
    ArrayList<Regla> reglas; //aqui se almacenan las reglas de todos los datos
    ArrayList<String> orden; //establece el orden de nodos del arbol
    boolean nuevaRegla; //indica si se trabaja en una nueva regla o en agrega información a una ya almacenada
    int profundidad; //indica cuan profunda es la recursividad

    public ID3(Datos data) {
        this.datos = data;
        titulos = datos.getTitulos();
        reglas = new ArrayList<Regla>();
        orden = new ArrayList<String>();
        nuevaRegla = true;
        profundidad = 0;
    }

    @Override
    public void Calcular() {
        Recurso(datos);
    }

    private void Recurso(Datos data) {

        profundidad++;

        //Si no hay datos termina.
        if (data.getLista().isEmpty()) {
            return;
        }

        String nombreClase = data.getNombreClase();
        ArrayList<Regla> reglasAcumuladas = new ArrayList<Regla>();
        ArrayList<TablaID3> tablas = new ArrayList<TablaID3>();
        ArrayList<ArrayList<String>> lista = data.getLista();
        ArrayList<Float> entropiaSistemas = new ArrayList<Float>();
        boolean completo = false;

        //Calcula las entropias de cada atributo
        for (int i = 0; i < titulos.length; i++) {

            //No calcula la entropia de la clase.
            if (titulos[i].equals(datos.getNombreClase())) {
                continue;
            }

            TablaID3 tabla = new TablaID3();
            for (int j = 0; j < lista.size(); j++) {
                ArrayList<String> registro = lista.get(j);
                String valor = registro.get(i);
                int indiceClase = getIndiceTitulo(titulos, datos.getNombreClase());
                String valorClase = registro.get(indiceClase);
                tabla.setNombreAtributo(titulos[i]);
                tabla.AgregarValor(valor, valorClase);
            }
            tablas.add(tabla);
            entropiaSistemas.add(tabla.getEntropia());
        }

        //Para calcular la entropia de cada valor de atributo
        float mayorGanancia = -1;
        TablaID3 mejorTabla = null;
        for (int i = 0; i < tablas.size(); i++) {
            float acumulado = 0;
            TablaID3 tablaID3 = tablas.get(i);
            ArrayList<TablaValoresID3> tablasValores = tablaID3.getTablasValores();
            for (int j = 0; j < tablasValores.size(); j++) {
                TablaValoresID3 tabla = tablasValores.get(j);
                int cantidad = tabla.getCantidadTotal();
                float resultado = cantidad * 1f / tablaID3.getCantidad() * tabla.getEntropia();
                acumulado += resultado;
            }
            float ganancia = entropiaSistemas.get(i) - acumulado;
            if (ganancia > mayorGanancia) {
                mayorGanancia = ganancia;
                mejorTabla = tablaID3;
            }
            if (ganancia == 0 && tablas.get(i).getTablasValores().size() == 1) {
                Regla r = new Regla();
                TablaValoresID3 valorUnico = tablaID3.getTablasValores().get(0);
                r.AgregarRegla(tablaID3.getNombreAtributo(), valorUnico.getValor());
                reglasAcumuladas.add(r);
            }
        }

        //Agrega el nuevo atributo al orden de los nodos del arbol.
        orden.add(mejorTabla.getNombreAtributo());

        //Busca el valor con menos entropía
        float menorEntropia = -1;
        TablaValoresID3 mejorTablaValores = null;
        ArrayList<TablaValoresID3> tablasValores = mejorTabla.getTablasValores();
        for (int i = 0; i < tablasValores.size(); i++) {
            TablaValoresID3 tabla = tablasValores.get(i);
            float entropia = tabla.getEntropia();
            if (menorEntropia == -1 || entropia < menorEntropia) {
                menorEntropia = entropia;
                mejorTablaValores = tabla;
                if (entropia == 0) {
                    completo = true;
                }
            }
        }

        //Si ya se evaluaron todos los atributos y ninguno tiene entropía 0, escoge el menos peor.
        if (profundidad >= titulos.length - 1) {
            completo = true;
        }

        //Encuentra el mejor valor de clase para el valor de atributo seleccionado.
        String valorClase = "";
        int mayorCantidad = -1;
        for (int i = 0; i < mejorTablaValores.getCantidadValoresClase(); i++) {
            int cant = mejorTablaValores.getCantidad(i);
            if (cant > mayorCantidad) {
                mayorCantidad = cant;
                valorClase = mejorTablaValores.getValorClase(i);
            }
        }

        //Agrega una nueva regla con los valores calculador hasta aqui.
        Regla r;
        if (nuevaRegla) {
            r = new Regla();
            r.setValorClase(valorClase);
            r.setNombreClase(datos.getNombreClase());

            for (int i = 0; i < orden.size(); i++) {
                for (int j = 0; j < reglasAcumuladas.size(); j++) {
                    if (reglasAcumuladas.get(j).getNombreAtributo().equals(orden.get(i))) {
                        r.AgregarRegla(reglasAcumuladas.get(j).getNombreAtributo(), reglasAcumuladas.get(j).getValor());
                        reglasAcumuladas.remove(j);
                        break;
                    }
                }
            }

            r.AgregarRegla(mejorTabla.getNombreAtributo(), mejorTablaValores.getValor());
            reglas.add(r);
        } else {
            r = reglas.get(reglas.size() - 1);
            r.AgregarRegla(mejorTabla.getNombreAtributo(), mejorTablaValores.getValor());
        }

        //aqui se llena una nueva lista con los valores que se analizaran en la recursividad
        if (!completo) {

            boolean[] probados = Comprobar(data, reglas.size() - 1);
            ArrayList<ArrayList<String>> nuevaLista = new ArrayList<ArrayList<String>>();

            for (int m = 0; m < probados.length; m++) {

                ArrayList<String> registro = lista.get(m);
                ArrayList<String> nValores = new ArrayList<String>();

                if (probados[m] == true) {
                    for (int j = 0; j < registro.size(); j++) {
                        nValores.add(registro.get(j));
                    }
                }

                if (nValores.size() > 0) {
                    nuevaLista.add(nValores);
                }
            }

            nuevaRegla = false;
            Datos nDatos = new Datos();
            nDatos.setNombreClase(nombreClase);
            nDatos.setLista(nuevaLista);
            nDatos.setTitulos(titulos);
            Recurso(nDatos);
            profundidad--;
            orden.remove(orden.size() - 1);
        } else {
            nuevaRegla = true;
        }

        //Examina para saber si el atributo analizado tiene mas valores no evaluados
        Regla valoresRestantes = new Regla();
        for (int i = 0; i < mejorTabla.getTablasValores().size(); i++) {
            String value = mejorTabla.getTablasValores().get(i).getValor();
            if (!value.equals(mejorTablaValores.getValor())) {
                valoresRestantes.AgregarRegla(mejorTabla.getNombreAtributo(), value);
            }
        }

        //Si hay valores no evaluados para el atributo actual, los analiza
        for (int k = 0; k < valoresRestantes.getCantidad(); k++) {
            ArrayList<ArrayList<String>> nuevaLista = new ArrayList<ArrayList<String>>();
            for (int i = 0; i < lista.size(); i++) {
                ArrayList<String> registro = lista.get(i);
                String atrib = valoresRestantes.getNombreAtributo(k);
                String val = valoresRestantes.getValor(k);
                int indiceAtributo = getIndiceTitulo(titulos, atrib);
                if (registro.get(indiceAtributo).equals(val)) {
                    nuevaLista.add(registro);
                }
            }
            Datos nDatos = new Datos();
            nDatos.setNombreClase(nombreClase);
            nDatos.setLista(nuevaLista);
            nDatos.setTitulos(titulos);
            Recurso(nDatos);
            profundidad--;
            orden.remove(orden.size() - 1);
        }

    }

    /**
     * Devuelve un vector de booleanos indicando cuáles de los elementos de la
     * lista cumplen las reglas
     *
     * @param data
     * @param desde
     * @return
     */
    private boolean[] Comprobar(Datos data, int desde) {
        ArrayList<ArrayList<String>> lista = data.getLista();
        String[] titulosDatos = data.getTitulos();
        boolean[] probados = new boolean[lista.size()];

        for (int j = desde; j < reglas.size(); j++) {
            Regla regla = reglas.get(j);
            for (int k = 0; k < lista.size(); k++) {
                probados[k] = true;
                ArrayList<String> registro = lista.get(k);
                int cantidad = regla.getCantidad();
                for (int num = 0; num < cantidad; num++) {
                    String nombreAtributo = regla.getNombreAtributo(num);
                    String valor = regla.getValor(num);
                    int index = getIndiceTitulo(titulosDatos, nombreAtributo);
                    String valorPrueba = registro.get(index);
                    if (!valorPrueba.equals(valor)) {
                        probados[k] = false;
                    }
                }
            }
        }
        return probados;
    }

    private int getIndiceTitulo(String[] titulos, String nombre) {
        for (int i = 0; i < titulos.length; i++) {
            if (titulos[i].equals(nombre)) {
                return i;
            }
        }

        return -1;
    }

    @Override
    public String LeerReglas() {
        //Agregado por esteban puello y raul piñeres
        MuestraArbol frame = new MuestraArbol(reglas);
        //Se acaba el codigo agregado por esteban puello y raul piñeres
        ArrayList<String> varianles = new ArrayList<String>();
        String filejava = "";
        Regla modelo = new Regla();
        modelo.AgregarRegla(reglas.get(0).getNombreAtributo(0), reglas.get(0).getValor(0));
        String regla = "";
        for (int i = 0; i < reglas.size(); i++) {
            regla += (i + 1) + ".";
            filejava += "if(";
            Regla r = reglas.get(i);
            int cantidad = r.getCantidad();
            for (int j = 0; j < cantidad; j++) {
                if (j > 0) {
                    regla += " &&";
                    filejava += " && ";
                }

                regla += " " + r.getNombreAtributo(j) + "=" + r.getValor(j);
                // regla +="(" + '"' + r.getValor(j) + '"' + ".equalsIgnoreCase(" + r.getNombreAtributo(j) + "))";
                filejava += "(" + '"' + r.getValor(j) + '"' + ".equalsIgnoreCase(" + r.getNombreAtributo(j) + "))";
            }

            regla += " ==> " + r.getNombreClase() + "=" + r.getValorClase() + "\n";
            filejava += "){label.setText(" + '"' + r.getValorClase() + '"' + ");}" + "\n";

        }
        Plantilla.setSentencias(filejava);
        return regla;
    }

    @Override
    public String LeerReglasProlog() {

        Regla modelo = new Regla();
        modelo.AgregarRegla(reglas.get(0).getNombreAtributo(0), reglas.get(0).getValor(0));



        //Campos seleccionados en la UI y su valores en la DB incluida la clase
        String camposConClase[] = formatear(datos.getTitulos());
        //Valores de esos campos

        //Un nuevo vector que almacenará los campos sin la clase
        String campos[] = new String[camposConClase.length - 1];
        ArrayList<ArrayList<String>> valoresCampos = datos.getPosiblesValores();

        int indiceCampo = 0;

        //Quitamos la clase de los campos
        for (int l = 0; l < camposConClase.length; l++) {

            if (!camposConClase[l].equals(formatear(datos.getNombreClase()))) {
                campos[indiceCampo++] = camposConClase[l];
            } else {
                valoresCampos.remove(l);
            }


        }






        char variables[] = new char[campos.length];

        //Sacamos la cantidad de variables necesarias del abecedario en MAYÚSCULAS
        for (int i = 65; i < 65 + campos.length; i++) {

            variables[i - 65] = (char) i;

        }

        //La variable que sirve para guardar el resultado en PROLOG
        char variableFinal = (char) ((char) variables[variables.length - 1] + 1);


        String regla = "";

        //Declaramos las proposiciones y sus posibles valores
        for (int i = 0; i < campos.length; i++) {

            for (int j = 0; j < valoresCampos.get(i).size(); j++) {
                regla += campos[i] + "(" + formatear(valoresCampos.get(i).get(j)) + ").\n";
            }

            regla += "\n";

        }

        //Declaramos la proposición de la clase
        regla += formatear(reglas.get(0).getNombreClase()) + "(";


        //Letras
        for (int i = 0; i < campos.length; i++) {

            regla += variables[i] + "";

            if (i != variables.length - 1) {
                regla += ", ";
            } else {
                regla += ", " + variableFinal + ") :- ";
            }
        }


        //Declaraciones
        for (int i = 0; i < campos.length; i++) {

            regla += campos[i] + "(" + variables[i] + "), ";

        }


        for (int i = 0; i < reglas.size(); i++) {

            Regla r = reglas.get(i);
            int cantidad = r.getCantidad();

            regla += "(";

            for (int j = 0; j < cantidad; j++) {



                if (j > 0) {
                    regla += ", ";
                }
                regla += "" + variables[this.indice(campos, formatear(r.getNombreAtributo(j)))] + "='" + formatear(r.getValor(j)) + "'";
            }

            regla += ")->" + variableFinal + "='" + formatear(r.getValorClase()) + "'";

            if (i < reglas.size() - 1) {
                regla += ";";
            } else {
                regla += ".";
            }

        }
        return regla;

    }

    //REPETIDO
    private int indice(String[] campos, String campo) {

        for (int i = 0; i < campos.length; i++) {
            if (campos[i].equals(campo)) {
                return i;
            }
        }

        return -1;

    }

    private String formatear(String valor) {
        return formatear(new String[]{valor})[0];
    }

    private String[] formatear(String[] valores) {
        String[] nuevo = new String[valores.length];
        for (int i = 0; i < valores.length; i++) {
            String valor = valores[i].toLowerCase();
            valor = valor.replace('ñ', 'n');
            valor = valor.replace('á', 'a');
            valor = valor.replace('é', 'e');
            valor = valor.replace('í', 'i');
            valor = valor.replace('ó', 'o');
            nuevo[i] = valor.replace('ú', 'u');
        }

        return nuevo;
    }

    @Override
    public Datos getDatos() {
        return datos;
    }
}
